package utils;

import java.util.ArrayList;

import rasterprimate.Constants;
import rasterprimate.FoodSourceMem;
import rasterprimate.FoodSourceMemTopo;
import rasterprimate.Primate;
import rasterprimate.PrimateTopo;
import rasterprimate.Resource;
import repast.simphony.space.gis.Geography;
import repast.simphony.space.grid.Grid;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;


public final strictfp class ForagingUtils {

	//(1)
	//Compares and finds the best resource site, either from memory or from visible sites.
	public static Resource chooseFeedingSite(Primate agent){

		Resource bestFoodSite = null;

		Resource bestRemembered = findBestKnownFoodSource(agent.getCoord(),agent.getMemory(),agent.getGrid());//find best remembered food source
		Resource bestNearMe = findBestUnknownFoodSource(agent.getCoord(),agent.getGeog());//find best nearby food source

		//1:Both a remembered and unknown site found
		if (bestRemembered!=null&&bestNearMe!=null){

			double remdist = agent.getCoord().distance(bestRemembered.getCoord());
			double unknowndist = agent.getCoord().distance(bestNearMe.getCoord());

			//only consider distance to remembered sites
			if(Constants.considerDistance== true){
				if (remdist<Constants.cellSize)remdist=Constants.cellSize;
				if (unknowndist<Constants.cellSize)unknowndist=Constants.cellSize;
			} else if (Constants.considerDistance == false){
				if (remdist<Constants.foodSearchRange)remdist=Constants.foodSearchRange;
				if (unknowndist<Constants.foodSearchRange)unknowndist=Constants.foodSearchRange;
			}

			double remResWeight = remdist/bestRemembered.getresLevel();
			double unknResWeight = unknowndist/bestNearMe.getresLevel();

			//1.1 remembered site is chosen
			if (remResWeight/Constants.weightOfMem<unknResWeight){//A remembered site is chosen:  wieghtOfKnownSites can be used to increase the importance of known sites

				if(GroupUtils.checkIfSafe(agent,bestRemembered.getCoord())==true){//safe to move... agent chooses this site
					//System.out.println("1.1 a remembered site was chosen");
					return bestRemembered;

				}else{ // not safe to move... agent looks for a resource on the way to goal
					Resource resTowardsGoal = chooseFoodSourceTowardsMyMigrationGoal(agent,bestRemembered.getCoord());
					if(resTowardsGoal!=null){  //found resource on the way to goal... agent chooses this intermediate resource
						//System.out.println("1.2: A migratory site was chose on way to remembered site "+ agent.idNumb);
						return resTowardsGoal;

					}else{
						System.out.println("1.3: a remembered site was choosen but no way to my migrate!! "+agent.idNumb);
						return agent.getMyPatch();
					}
				}
				//2.0: A unknown nearby food site is chosen
			}else{

				if(GroupUtils.checkIfSafe(agent,bestNearMe.getCoord())==true){
					//System.out.println("2.1: An unknown site was choosen "+ agent.idNumb);
					return bestNearMe;
				}else{
					Resource resTowardsGoal = chooseFoodSourceTowardsMyMigrationGoal(agent,bestNearMe.getCoord());

					if(resTowardsGoal!=null){  
						//	System.out.println("2.2: A migritory site to a nearby site was choosen "+ agent.idNumb);
						return resTowardsGoal;
					}else{
						//setHaveTargetFoodSource(false);
						System.out.println("2.3: a nearby site was choosen but no way to my migrate!! "+agent.idNumb);
						return agent.getMyPatch();
					}
				}
			}

			//2: There are no sites available
		}else if (bestRemembered==null&&bestNearMe==null){
			//System.out.println("3: No site was chosen " + agent.idNumb);
			return agent.getMyPatch();
			//3: There are no remembered sites; an unknown food source is chosen
		}else if (bestRemembered==null){

			if(GroupUtils.checkIfSafe(agent,bestNearMe.getCoord())==true){
				//System.out.println("3.1: An unknown site was choosen "+ agent.idNumb);
				return bestNearMe;
			}else{
				Resource resTowardsGoal = chooseFoodSourceTowardsMyMigrationGoal(agent,bestNearMe.getCoord());
				if(resTowardsGoal!=null){  
					//System.out.println("3.2: A migratory site was chosen to a nearby site "+ agent.idNumb);
					return resTowardsGoal;
				}else{
					System.out.println("3.3: a nearby site was choosen but no way to my migrate!! "+agent.idNumb);
					return agent.getMyPatch();
				}
			}

			//4: There are no nearby sites; a remembered food source is chosen
		}else if (bestNearMe==null){

			if(GroupUtils.checkIfSafe(agent,bestRemembered.getCoord())==true){
				//	System.out.println("4.1: A remembered site was choosen "+ agent.idNumb);
				return bestRemembered;
			}else{
				Resource resTowardsGoal = chooseFoodSourceTowardsMyMigrationGoal(agent,bestRemembered.getCoord());
				if(resTowardsGoal!=null){  
					//	System.out.println("4.2: A migratory site was chosen to a remembered site "+ agent.idNumb);
					return resTowardsGoal;
				}else{
					System.out.println("4.3: a remembered site was choosen but no way to my migrate!! "+agent.idNumb);
					return agent.getMyPatch();
				}
			}
		}else{
			System.out.println("5.0: Error in chooseFoodSource() within foragingUtils class");
		}

		return bestFoodSite;

	}

	//(2)
	public static Resource chooseFoodSourceTowardsMyMigrationGoal(Primate agent,Coordinate bestRes){//in case the primate has a goal to migrate towards; chose best site in that direction

		Resource towardsMyGoal=null;
		boolean found=false;

		double dist=9999,newDist=9999,resourceWeight=9999;
		double minResourceWeight=99999;
		Resource bestCloseSite=null;

		//Find all resources within my search range
		Envelope foodSearch = new Envelope();
		foodSearch.init(agent.getCoord());
		foodSearch.expandBy(Constants.foodSearchRange*4);///degToMeters);
		Iterable<Resource> primateFood = agent.getGeog().getObjectsWithin(foodSearch,Resource.class);
		foodSearch.setToNull();
		int count = 0;

		//choose the "best" site; distance vs food + is safe
		for (Resource r:primateFood){


			if(agent.getCoord().distance(r.getCoord())<=Constants.foodSearchRange){
				count++;
				dist = agent.getCoord().distance(bestRes);
				newDist = r.getCoord().distance(bestRes);

				if (agent.getCoord().distance(r.getCoord()) <= Constants.foodSearchRange && dist>newDist){ 
					resourceWeight=(r.getCoord().distance(bestRes))/r.getresLevel();//choose safe site that is the closest to the migration goal
					if(resourceWeight<minResourceWeight){
						//if(GroupUtils.checkIfSafe(agent,r.coord)==true){
						minResourceWeight = resourceWeight;
						bestCloseSite = r;
						found=true;
						//}
					}
				}
			}
		}
		//System.out.println("Migration goal: nearby possible targets = "+count);
		if (found == true)towardsMyGoal=bestCloseSite;//return site; if a acceptable site has been chosen


		if(towardsMyGoal==null){
			//System.out.println("Tried to find a site towards my goal but non available!! "+ agent.idNumb);
			//MoveUtils.moveTowards(agent, bestRes);
			towardsMyGoal = agent.getMyPatch();
		}


		return towardsMyGoal;
	}


	//(3)
	//finds the best resource within an agents memory
	private static Resource findBestKnownFoodSource(Coordinate myP, ArrayList<FoodSourceMem> mem, Grid grid){ //find best remembered site

		double dist=999999,resourceWeight=999999;
		double minResourceWeight=9999999;
		Resource bestRes = null;

		//compare known food sources by comparing site weights; lowest one is the best
		for(FoodSourceMem f:mem){

			//calculate distance to remembered site
			dist = myP.distance(f.getCoord());

			// if primate can "see" the remembered resource (within sensory range)
			if (dist<=Constants.foodSearchRange){
				if(Constants.considerDistance==true){
					if (dist<Constants.cellSize)dist=Constants.cellSize;
				} else if (Constants.considerDistance == false){
					if (dist<Constants.foodSearchRange)dist=Constants.foodSearchRange;
				}
				//food = geog.getResourceLevelAtThisCoord(f.getCoord());
				Resource seenRes = (Resource)grid.getObjectAt(f.getGridX(),f.getGridY()); //find resource at remembered site
				f.setFoodRemembered(seenRes.getresLevel());  // update memory, as primate can see the resource
				resourceWeight=dist/(seenRes.getresLevel()); //calculate the resource weight of the site

				// if primate cannot "see" the remembered resource (outside sensory range)
			}else{
				resourceWeight=dist/f.getFoodRemembered();//primate remembers past resource level and estimates what it is now
			}

			//compare weights and update bestRes at the site with the lowest resource weight (dist/food)
			if (resourceWeight<minResourceWeight){  
				minResourceWeight=resourceWeight;
				bestRes = (Resource)grid.getObjectAt(f.getGridX(),f.getGridY());
			}
		}

		return bestRes;
	}

	//finds the best resource that the agent can "see"
	private static Resource findBestUnknownFoodSource(Coordinate myP, Geography geog){//find the best unknown resource; all resources within search radius

		double dist=9999,resourceWeight=9999,minResourceWeight=999999;
		Resource bestCloseSite=null;

		//Find all resources within my search radius
		Envelope foodSearch = new Envelope();
		foodSearch.init(myP);
		foodSearch.expandBy(Constants.foodSearchRange*4);///degToMeters);
		Iterable<Resource> primateFood = geog.getObjectsWithin(foodSearch,Resource.class);



		//Calculate each resources food weight and choose the lowest one (weight = distance / food)
		for (Resource r:primateFood){

			if(myP.distance(r.getCoord())<=Constants.foodSearchRange){
				dist = myP.distance(r.getCoord());
				if (dist < Constants.foodSearchRange){
					if(Constants.considerDistance==true){
						if (dist<Constants.cellSize)dist=Constants.cellSize;
					}else{
						if (dist<Constants.foodSearchRange)dist=Constants.foodSearchRange;
					}
					resourceWeight=dist/r.getresLevel();
					if(resourceWeight<minResourceWeight ){
						minResourceWeight = resourceWeight;
						bestCloseSite = r;
					}
				}
			}
		}

		foodSearch.setToNull();
		return bestCloseSite;
	}

	/***************************************************TOPOLOGICAL************************************************/

	//(1)
	//Compares and finds the best resource site, either from memory or from visible sites.
	public static Resource chooseFeedingSite(PrimateTopo agent){

		Resource bestFoodSite = null;

		Resource bestRemembered = findBestKnownFoodSourceTopo(agent.getCoord(),agent.getLastRememberedSite(),agent.getGrid());//find best remembered food source
		Resource bestNearMe = findBestUnknownFoodSourceTopo(agent.getCoord(),agent.getGeog(),agent.getMemory(),agent);//find best nearby food source

		//1:Both a remembered and unknown site found
		if (bestRemembered!=null&&bestNearMe!=null){

			double remdist = agent.getCoord().distance(bestRemembered.getCoord());
			double unknowndist = agent.getCoord().distance(bestNearMe.getCoord());

			if(Constants.considerDistance==true){
				if (remdist<Constants.cellSize)remdist=Constants.cellSize;
				if (unknowndist<Constants.cellSize)unknowndist=Constants.cellSize;
			}else if (Constants.considerDistance==false){
				if (remdist<Constants.foodSearchRange)remdist=Constants.foodSearchRange;
				if (unknowndist<Constants.foodSearchRange)unknowndist=Constants.foodSearchRange;
			}


			double remResWeight = remdist/bestRemembered.getresLevel();
			double unknResWeight = unknowndist/bestNearMe.getresLevel();

			//1.1 remembered site is chosen
			if (remResWeight/Constants.weightOfMem<unknResWeight){//A remembered site is chosen:  wieghtOfKnownSites can be used to increase the importance of known sites

				if(GroupUtils.checkIfSafe(agent,bestRemembered.getCoord())==true){//safe to move... agent chooses this site
					//		System.out.println("1.1 a remembered site was chosen");
					return bestRemembered;

				}else{ // not safe to move... agent looks for a resource on the way to goal
					Resource resTowardsGoal = chooseFoodSourceTowardsMyMigrationGoal(agent,bestRemembered.getCoord());
					if(resTowardsGoal!=null){  //found resource on the way to goal... agent chooses this intermediate resource
						//		System.out.println("1.2: A migratory site was chose on way to remembered site "+ agent.idNumb);
						return resTowardsGoal;

					}else{
						//	System.out.println("1.3: a remembered site was choosen but no way to my migrate!! "+agent.idNumb);
						return agent.getMyPatch();
					}
				}
				//2.0: A unknown nearby food site is chosen
			}else{

				if(GroupUtils.checkIfSafe(agent,bestNearMe.getCoord())==true){
					//	System.out.println("2.1: An unknown site was choosen "+ agent.idNumb);
					return bestNearMe;
				}else{
					Resource resTowardsGoal = chooseFoodSourceTowardsMyMigrationGoal(agent,bestNearMe.getCoord());

					if(resTowardsGoal!=null){  
						//	System.out.println("2.2: A migritory site to a nearby site was choosen "+ agent.idNumb);
						return resTowardsGoal;
					}else{
						//setHaveTargetFoodSource(false);
						//System.out.println("2.3: a nearby site was choosen but no way to my migrate!! "+agent.idNumb);
						return agent.getMyPatch();
					}
				}
			}

			//2: There are no sites available
		}else if (bestRemembered==null&&bestNearMe==null){
			//	System.out.println("3: No site was chosen " + agent.idNumb);
			return agent.getMyPatch();
			//3: There are no remembered sites; an unknown food source is chosen
		}else if (bestRemembered==null){

			if(GroupUtils.checkIfSafe(agent,bestNearMe.getCoord())==true){
				//	System.out.println("3.1: An unknown site was choosen "+ agent.idNumb);
				return bestNearMe;
			}else{
				Resource resTowardsGoal = chooseFoodSourceTowardsMyMigrationGoal(agent,bestNearMe.getCoord());
				if(resTowardsGoal!=null){  
					//	System.out.println("3.2: A migratory site was chosen to a nearby site "+ agent.idNumb);
					return resTowardsGoal;
				}else{
					//System.out.println("3.3: a nearby site was choosen but no way to my migrate!! "+agent.idNumb);
					return agent.getMyPatch();
				}
			}

			//4: There are no nearby sites; a remembered food source is chosen
		}else if (bestNearMe==null){

			if(GroupUtils.checkIfSafe(agent,bestRemembered.getCoord())==true){
				//	System.out.println("4.1: A remembered site was choosen "+ agent.idNumb);
				return bestRemembered;
			}else{
				Resource resTowardsGoal = chooseFoodSourceTowardsMyMigrationGoal(agent,bestRemembered.getCoord());
				if(resTowardsGoal!=null){  
					//	System.out.println("4.2: A migratory site was chosen to a remembered site "+ agent.idNumb);
					return resTowardsGoal;
				}else{
					//System.out.println("4.3: a remembered site was choosen but no way to my migrate!! "+agent.idNumb);
					return agent.getMyPatch();
				}
			}
		}else{
			//System.out.println("5.0: Error in chooseFoodSource() within foragingUtils class");
		}



		return bestFoodSite;

	}


	//(2)
	public static Resource chooseFoodSourceTowardsMyMigrationGoal(PrimateTopo agent,Coordinate bestRes){//in case the primate has a goal to migrate towards; chose best site in that direction

		Resource towardsMyGoal=null;
		boolean found=false;

		double dist=9999,newDist=9999,resourceWeight=9999;
		double minResourceWeight=99999;
		Resource bestCloseSite=null;

		//Find all resources within my search range
		Envelope foodSearch = new Envelope();
		foodSearch.init(agent.getCoord());
		foodSearch.expandBy(Constants.foodSearchRange*4);///degToMeters);
		Iterable<Resource> primateFood = agent.getGeog().getObjectsWithin(foodSearch,Resource.class);

		//choose the "best" site; distance vs food + is safe
		for (Resource r:primateFood){
			dist = agent.getCoord().distance(bestRes);
			newDist = r.getCoord().distance(bestRes);

			if (agent.getCoord().distance(r.getCoord()) <= Constants.foodSearchRange && dist>newDist){ 
				resourceWeight=(r.getCoord().distance(bestRes));//choose safe site that is the closest to the migration goal
				if(resourceWeight<minResourceWeight){
					//if(GroupUtils.checkIfSafe(agent,r.coord)==true){
						minResourceWeight = resourceWeight;
						bestCloseSite = r;
						found=true;
					//}
				}
			}
		}

		if (found == true)towardsMyGoal=bestCloseSite;//return site; if a acceptable site has been chosen


		if(towardsMyGoal==null){
			//System.out.println("Tried to find a site towards my goal but non available!! "+ agent.idNumb);
			towardsMyGoal = agent.getMyPatch();
		}
		foodSearch.setToNull();
		return towardsMyGoal;
	}

	//(3)
	//finds the best resource within an agents memory
	private static Resource findBestKnownFoodSourceTopo(Coordinate myP, FoodSourceMemTopo lastMem, Grid grid){ //find best remembered site

		double dist=999999,resourceWeight=999999;
		double minResourceWeight=9999999;
		Resource bestRes = null;

		//compare known food sources by comparing site weights; lowest one is the best
		try {
			for(FoodSourceMemTopo f:lastMem.getNearBySites()){

				//calculate distance to remembered site
				dist = myP.distance(f.getCoord());

				// if primate can "see" the remembered resource (within sensory range)
				if (dist<=Constants.foodSearchRange){
					if (Constants.considerDistance==true){
						if (dist<Constants.cellSize)dist=Constants.cellSize;
					} else if (Constants.considerDistance==false){
						if (dist<Constants.foodSearchRange)dist=Constants.foodSearchRange;
					}
					//food = geog.getResourceLevelAtThisCoord(f.getCoord());
					Resource seenRes = (Resource)grid.getObjectAt(f.getGridX(),f.getGridY()); //find resource at remembered site
					f.setFoodRemembered(seenRes.getresLevel());  // update memory, as primate can see the resource
					resourceWeight=dist/(seenRes.getresLevel()); //calculate the resource weight of the site

					// if primate cannot "see" the remembered resource (outside sensory range)
				}else{
					resourceWeight=dist/f.getFoodRemembered();//primate remembers past resource level and estimates what it is now
				}

				//compare weights and update bestRes at the site with the lowest resource weight (dist/food)
				if (resourceWeight<minResourceWeight){  
					minResourceWeight=resourceWeight;
					bestRes = (Resource)grid.getObjectAt(f.getGridX(),f.getGridY());
				}
			}
		} catch (Exception NullPointerException) {
			// TODO Auto-generated catch block
			//System.out.println("no memory set as my last memory site, yet...");
		}

		return bestRes;
	}


	//finds the best resource that the agent can "see"
	private static Resource findBestUnknownFoodSourceTopo(Coordinate myP, Geography geog, ArrayList<FoodSourceMemTopo> familarSites,PrimateTopo agent){//find the best unknown resource; all resources within search radius

		double dist=9999,resourceWeight=9999,minResourceWeight=999999;
		Resource bestCloseSite=null;

		//Find all resources within my search radius
		Envelope foodSearch = new Envelope();
		foodSearch.init(myP);
		foodSearch.expandBy(Constants.foodSearchRange*4);///degToMeters);
		Iterable<Resource> primateFood = geog.getObjectsWithin(foodSearch,Resource.class);

		//Calculate each resources food weight and choose the lowest one (weight = distance / food)
		for (Resource r:primateFood){
			dist = myP.distance(r.getCoord());
			if(myP.distance(r.getCoord())<=Constants.foodSearchRange){
				if (dist < Constants.foodSearchRange){
					if(Constants.considerDistance==true){
						if (dist<Constants.cellSize)dist=Constants.cellSize;
					}else{
						if (dist<Constants.foodSearchRange)dist=Constants.foodSearchRange;
					}
					resourceWeight=dist/r.getresLevel();
					if(resourceWeight<minResourceWeight ){
						minResourceWeight = resourceWeight;
						bestCloseSite = r;
					}
				}
			}
		}

		//Test to see if any of these new visible sites are familar, and include associated nearby sites.
		for (Resource r:primateFood){

			//compare all visual sites with familar sites
			for (FoodSourceMemTopo fm : familarSites){
				//if any are familar then calculate their desireability
				if(r==fm.getResource()){
					for (FoodSourceMemTopo nearSites: fm.getNearBySites()){

						Resource nearR = fm.getResource();
						dist = myP.distance(nearR.getCoord());
						if (dist < Constants.foodSearchRange){
							if(Constants.considerDistance==true){
								if (dist<Constants.cellSize)dist=Constants.cellSize;
							}else{
								if (dist<Constants.foodSearchRange)dist=Constants.foodSearchRange;
							}
							resourceWeight=dist/nearR.getresLevel();
							if(resourceWeight<minResourceWeight ){
								minResourceWeight = resourceWeight;
								bestCloseSite = nearR;
								agent.setLastRememberedSite(fm);
							}
						}
					}
				}
			}
		}

		foodSearch.setToNull();

		//return best visible site or associated site
		return bestCloseSite;
	}
}
